Step by Step: Developing a Media Center-esque list/table widget
= Introduction = Motivation / Goal I have a need for a Windows XP Media Center-like table or list. I'm talking about the root-level table that comes up when Media center is first started. You can see Microsoft's own emulation of it from their product site. Note that this is only for the component itself, not the background or what happens when selection changes or when a row is clicked -- but we will need to support those things. Requirements If you have played around with Media Center or with the demo above, you will know that there is quite a lot of behavior to match. Let's try to enumerate what we expect out of this component: # Input ## Support for keyboard -- a remote falls under this, since it is a safe assumption that a remote can be emulated as a keyboard. Up, down, select (enter or space, perhaps) are requirements, others may be nice to have. ## Support for mouse ### Mouse clicks -- you can click anything on the screen, even if it is not currently highlighted/selected. Note that while this click does change selection, it actually triggers an action on the clicked object as well. ### Mouseover -- some mouseover effects are present. This is not a hard requirement for me, so these will not be implemented. # Display ## Limitless -- List items wrap seemlessly. User can continue indefinitely up or down. Note that there may be an additional spacer upon list wrap. ## Sliding -- smooth animated motions for the text. As up and down are pressed, the text slides under and through the highlight box. ## Row detail -- Rows generally consist of text, but we may want to allow more detail. The Highlighted row should certainly have an option to display more. ## Highlight/selection -- There is always a selected row. With keyboard motion, this highlighted box stays centered in the list, and the text scrolls through it. A mouse click elsewhere on the list will move the highlight box and start an action (see Action Feedback, below) ## Action feedback -- a small animation plays when an item in the list is acted upon, via mouse click or keyboard select. ## Fading items -- Central items are displayed in a crisp font and color. Items at the edge of the list slowly fade to transparent. ## Scrollable indicators -- Not on the MC list as displayed in the screenshot above, but having the option to turn on some kind of arrows indicating more content above and below would be nice. These arrows could also provide scrolling support when a keyboard is not available (like a Kiosk). # Code Usability ## Reusable component -- self-contained in its own file ## Provides notifications of up/down/select actions ## Can interface with Java, perhaps via a PropertyChangeListener = Building the MediaTable Widget = The Basics A very rough attempt We have a long road ahead of us. To start out, I just decided to see if I can get a text array to show up. Not too bad... I have a suspicion that rowNum will cause us some trouble if we are trying to dynamically add new rows, but it will do for now. Some Cleanup That code was simplistic, and not too usable. Let's start packaging the class for reuse. I have taken out some temporary code and values, and made MediaTable a full-fledged Node object that can be included easily in other Nodes. Though it does nothing at this point, you can see that the intention is to constrain the MediaTable to a certain size -- inside the rectangle. It will then do what is needed at the edges (transparency fadeout, etc) and will hopefully respect the place it is given. The intention is that MediaTable will always work with components in its own 0,0 space. You then translate it when you use it, as shown here. Clipping and the Start of something big First off, let's fix clipping, so that the text is no longer scrolling off the bottom. Also, let's have a closer tie to the border, so I'll bind the translate to the border's x and y values. Lastly, let's do some ground work for calculating the position of the center row and showing a highlight. As you can see, we're not quite there, especially as the highlight doesn't seem to be centered as expected. But there are some interesting things. Functions (which appear to be the parts that aren't working), new attributes. I'm also beginning to wonder if MediaTable should take a Shape to clip on, and not width, height, and transform, done externally. Calculations and scrolling Let's fix the highlight row, add support for changing the center / selected row, and continute to tighten up the core MediaTable class. It turns out the highlight row problem was one of syntax; in function definitions, MediaTable.rowHeight and rowHeight are different. I suspect this is a static-versus-instance sort of thing. I moved currentRow (which was unused before) from the table model into the table itself, since it really is a view feature. I also changed the attributes on the table to take a Rect instead of x, y, width, height, etc to facilitate easier usage. For the GUI itself, I was able to get rid of the pesky rowNum variable -- turns out in a foreach you can simply do a indexof to get the index of the current loop variable. The highlight rectangle has changed a bit, and has a fill that lies under the text. Finally, I've added two rectangles, that, when clicked, act as up and down operations. Some next steps: * The up/down ops need to be internalized into the MediaTable itself, exposed as real operations. * Row sliding animation * Row wrapping Some of these, wrapping in particular, may require some radical changes. In particular, we may have to divorce the rows variable from the row objects (Texts, right now) that are created. We may need two, three, or more copies of a row GUI object depending on the size of the data and its clipping area. Wrapping Woah, this is not so easy. There are still plenty of quirks and bugs in JavaFX, so you have to save often while using JavaFX Pad. I don't yet have sliding working -- I still have a feeling I need a more formalized View Model, or something. So, there are now operations for moving up and down. Scrolling works pretty seamlessly, by essentially converting out-of-range rows back into in-range values. The normalizeRow() method has the logic for this. Notice the intValue() calls when defining topRow and bottomRow. Since the top and bottom rows could be fractional, I was having a lot of trouble getting pure ints to iterate over for deciding which rows to draw. These calls helped me clear up the issue, and then the later foreach and array access were simple. Making a component you want to use Don't Fade Away A lot of big changes coming in here. Keyboard support, though not arrow keys. More stratification into classes. More configurable parameters. The demonstration code is now formally split out into its own code. Here is the new test file: As you can see, there are many more row rendering attributes that can be specified. Unfortunately, I could not really get sliding animation working yet. Row fading is in there, but I'm not quite happy with it... I'd like a better fade out than a per-line fade. On the plus side, it handles different table heights with no problems. Keyboard is in there, barely, but only takes affect after a click on the red boxes... and since the arrow keys are not detected, I use w''' and '''s. Checkpoint Let's see how we are doing so far with our list of requirements: # Input ## Support for keyboard -- Moderate. Arrow keys don't work, generally support is iffy ## Support for mouse ### x Mouse clicks -- Looking fine, working as expected ### Mouseover -- # Display ## x Limitless -- Working by "tricking" data model ## Sliding -- ## Row detail -- Moderate. Additional parameters are available allowing customization, but we still may need a more formal row rendering ability. ## Highlight/selection -- Moderate. There is always a selection, but it does not move in response to an off-selected click; instead, the row clicked on becomes the center row. ## Action feedback -- ## Fading items -- Moderate. Each line of text is at a different alpha level. There is not a smooth fade to background for the farthest rows. ## Scrollable indicators -- # Code Usability ## x Reusable component -- As we want, it is self-contained in its own file. It has several extra classes that control its behavior. ## x Provides notifications -- Mostly complete. Code can bind to the centerRow attribute for scrolling notifications, and can set the rowSelected operation to be notified of selection actions. ## Can interface with Java -- Down the slide Let's fix this sliding thing. Run in combination with TestMediaTable, above. Major changes are to the class definition (adding new private attributes for animation), the moveUp(), moveDown() operations, and the rowOffset() function. Notice the syntax I use to interrupt animations in moveUp() and moveDown()... I increment a counter and tie the animation to that counter. If no animation is in progress and one of these operations is called, the counter starts over. This gives a reasonably smooth transition if the button is clicked multiple times rapidly. The biggest annoyance is that, since centerRow is changing immediately, but the display of the row is still in motion, combined with the fading solution we are using means the row alpha coloration moves off center. Summary This article was developed for a very old version of JFX. It will not be completed. Category:Tutorial